/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiumjdbc;

import java.sql.ClientInfoStatus;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverPropertyInfo;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowIdLifetime;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Value;

/**
 * This class provides conversion functions for <tt>CadmiumJDBC</tt> concrete types.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.1
 */
final class Misc {

    /** Kinds of nullable. */
    static enum NullableKind { RESULT_SET_META_DATA, PARAMETER_META_DATA };

    /**
     * No instance of this class.
     */
    private Misc() {
    } // end empty constructor

    /**
     * Converts a JDBC type from Java into OCaml representation.
     * @param t JDBC type, in Java representation
     * @return the passed JDBC type, in OCaml representation
     */
    static Value encodeJDBCType(final int t) {
        switch (t) {
        case Types.ARRAY: return Value.ZERO;
        case Types.BIGINT: return Value.ONE;
        case Types.BINARY: return Value.TWO;
        case Types.BIT: return Value.createFromLong(3);
        case Types.BLOB: return Value.createFromLong(4);
        case Types.BOOLEAN: return Value.createFromLong(5);
        case Types.CHAR: return Value.createFromLong(6);
        case Types.CLOB: return Value.createFromLong(7);
        case Types.DATALINK: return Value.createFromLong(8);
        case Types.DATE: return Value.createFromLong(9);
        case Types.DECIMAL: return Value.createFromLong(10);
        case Types.DISTINCT: return Value.createFromLong(11);
        case Types.DOUBLE: return Value.createFromLong(12);
        case Types.FLOAT: return Value.createFromLong(13);
        case Types.INTEGER: return Value.createFromLong(14);
        case Types.JAVA_OBJECT: return Value.createFromLong(15);
        case Types.LONGNVARCHAR: return Value.createFromLong(16);
        case Types.LONGVARBINARY: return Value.createFromLong(17);
        case Types.LONGVARCHAR: return Value.createFromLong(18);
        case Types.NCHAR: return Value.createFromLong(19);
        case Types.NCLOB: return Value.createFromLong(20);
        case Types.NUMERIC: return Value.createFromLong(21);
        case Types.NVARCHAR: return Value.createFromLong(22);
        case Types.OTHER: return Value.createFromLong(23);
        case Types.REAL: return Value.createFromLong(24);
        case Types.REF: return Value.createFromLong(25);
        case Types.ROWID: return Value.createFromLong(26);
        case Types.SMALLINT: return Value.createFromLong(27);
        case Types.SQLXML: return Value.createFromLong(28);
        case Types.STRUCT: return Value.createFromLong(29);
        case Types.TIME: return Value.createFromLong(30);
        case Types.TIMESTAMP: return Value.createFromLong(31);
        case Types.TINYINT: return Value.createFromLong(32);
        case Types.VARBINARY: return Value.createFromLong(33);
        case Types.VARCHAR: return Value.createFromLong(34);
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeJDBCType(int)'

    /**
     * Converts a JDBC type from OCaml into Java representation.
     * @param t JDBC type, in OCaml representation
     * @return the passed JDBC type, in Java representation
     */
    static int decodeJDBCType(final Value t) {
        switch (t.asLong()) {
        case 0: return Types.ARRAY;
        case 1: return Types.BIGINT;
        case 2: return Types.BINARY;
        case 3: return Types.BIT;
        case 4: return Types.BLOB;
        case 5: return Types.BOOLEAN;
        case 6: return Types.CHAR;
        case 7: return Types.CLOB;
        case 8: return Types.DATALINK;
        case 9: return Types.DATE;
        case 10: return Types.DECIMAL;
        case 11: return Types.DISTINCT;
        case 12: return Types.DOUBLE;
        case 13: return Types.FLOAT;
        case 14: return Types.INTEGER;
        case 15: return Types.JAVA_OBJECT;
        case 16: return Types.LONGNVARCHAR;
        case 17: return Types.LONGVARBINARY;
        case 18: return Types.LONGVARCHAR;
        case 19: return Types.NCHAR;
        case 20: return Types.NCLOB;
        case 21: return Types.NUMERIC;
        case 22: return Types.NVARCHAR;
        case 23: return Types.OTHER;
        case 24: return Types.REAL;
        case 25: return Types.REF;
        case 26: return Types.ROWID;
        case 27: return Types.SMALLINT;
        case 28: return Types.SQLXML;
        case 29: return Types.STRUCT;
        case 30: return Types.TIME;
        case 31: return Types.TIMESTAMP;
        case 32: return Types.TINYINT;
        case 33: return Types.VARBINARY;
        case 34: return Types.VARCHAR;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeJDBCType(Value)'

    /**
     * Converts a client info status from Java into OCaml representation.
     * @param t client info status, in Java representation
     * @return the passed client info status, in OCaml representation
     */
    static Value encodeClientInfoStatus(final ClientInfoStatus t) {
        switch (t) {
        case REASON_UNKNOWN:
            return Value.ZERO;
        case REASON_UNKNOWN_PROPERTY:
            return Value.ONE;
        case REASON_VALUE_INVALID:
            return Value.TWO;
        case REASON_VALUE_TRUNCATED:
            return Value.createFromLong(3);
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeClientInfoStatus(ClientInfoStatus)'

    /**
     * Converts a client info status from OCaml into Java representation.
     * @param t client info status, in OCaml representation
     * @return the passed client info status, in Java representation
     */
    static ClientInfoStatus decodeClientInfoStatus(final Value t) {
        switch (t.asLong()) {
        case 0: return ClientInfoStatus.REASON_UNKNOWN;
        case 1: return ClientInfoStatus.REASON_UNKNOWN_PROPERTY;
        case 2: return ClientInfoStatus.REASON_VALUE_INVALID;
        case 3: return ClientInfoStatus.REASON_VALUE_TRUNCATED;
        default:
            // never reached
            assert false : "invalid case";
            return null;
        } // end switch
    } // end method 'decodeClientInfoStatus(Value)'

    /**
     * Converts a row id lifetime from Java into OCaml representation.
     * @param t row id lifetime, in Java representation
     * @return the passed row id lifetime, in OCaml representation
     */
    static Value encodeRowIdLifetime(final RowIdLifetime t) {
        switch (t) {
        case ROWID_UNSUPPORTED:
            return Value.ZERO;
        case ROWID_VALID_FOREVER:
            return Value.ONE;
        case ROWID_VALID_OTHER:
            return Value.TWO;
        case ROWID_VALID_SESSION:
            return Value.createFromLong(3);
        case ROWID_VALID_TRANSACTION:
            return Value.createFromLong(4);
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeRowIdLifetime(RowIdLifetime)'

    /**
     * Converts a row id lifetime from OCaml into Java representation.
     * @param t row id lifetime, in OCaml representation
     * @return the passed row id lifetime, in Java representation
     */
    static RowIdLifetime decodeRowIdLifetime(final Value t) {
        switch (t.asLong()) {
        case 0: return RowIdLifetime.ROWID_UNSUPPORTED;
        case 1: return RowIdLifetime.ROWID_VALID_FOREVER;
        case 2: return RowIdLifetime.ROWID_VALID_OTHER;
        case 3: return RowIdLifetime.ROWID_VALID_SESSION;
        case 4: return RowIdLifetime.ROWID_VALID_TRANSACTION;
        default:
            // never reached
            assert false : "invalid case";
            return null;
        } // end switch
    } // end method 'decodeRowIdLifetime(Value)'

    /**
     * Converts a transaction isolation from Java into OCaml representation.
     * @param t transaction isolation, in Java representation
     * @return the passed transaction isolation, in OCaml representation
     */
    static Value encodeTransactionIsolation(final int t) {
        switch (t) {
        case Connection.TRANSACTION_NONE: return Value.ZERO;
        case Connection.TRANSACTION_READ_COMMITTED: return Value.ONE;
        case Connection.TRANSACTION_READ_UNCOMMITTED: return Value.TWO;
        case Connection.TRANSACTION_REPEATABLE_READ: return Value.createFromLong(3);
        case Connection.TRANSACTION_SERIALIZABLE: return Value.createFromLong(4);
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeTransactionIsolation(int)'

    /**
     * Converts a transaction isolation from OCaml into Java representation.
     * @param t transaction isolation, in OCaml representation
     * @return the passed transaction isolation, in Java representation
     */
    static int decodeTransactionIsolation(final Value t) {
        switch (t.asLong()) {
        case 0: return Connection.TRANSACTION_NONE;
        case 1: return Connection.TRANSACTION_READ_COMMITTED;
        case 2: return Connection.TRANSACTION_READ_UNCOMMITTED;
        case 3: return Connection.TRANSACTION_REPEATABLE_READ;
        case 4: return Connection.TRANSACTION_SERIALIZABLE;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeTransactionIsolation(Value)'

    /**
     * Converts a result set concurrency from Java into OCaml representation.
     * @param rsc result set concurrency, in Java representation
     * @return the passed result set concurrency, in OCaml representation
     */
    static Value encodeResultSetConcurrency(final int rsc) {
        switch (rsc) {
        case ResultSet.CONCUR_READ_ONLY: return Value.ZERO;
        case ResultSet.CONCUR_UPDATABLE: return Value.ONE;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeResultSetConcurrency(int)'

    /**
     * Converts a result set concurrency from OCaml into Java representation.
     * @param rsc result set concurrency, in OCaml representation
     * @return the passed result set concurrency, in Java representation
     */
    static int decodeResultSetConcurrency(final Value rsc) {
        switch (rsc.asLong()) {
        case 0: return ResultSet.CONCUR_READ_ONLY;
        case 1: return ResultSet.CONCUR_UPDATABLE;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeResultSetConcurrency(Value)'

    /**
     * Converts a result set fetch direction from Java into OCaml representation.
     * @param fo result set fetch direction, in Java representation
     * @return the passed result set fetch direction, in OCaml representation
     */
    static Value encodeResultSetFetchDirection(final int fo) {
        switch (fo) {
        case ResultSet.FETCH_FORWARD: return Value.ZERO;
        case ResultSet.FETCH_REVERSE: return Value.ONE;
        case ResultSet.FETCH_UNKNOWN: return Value.TWO;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeResultSetFetchDirection(int)'

    /**
     * Converts a result set fetch direction from OCaml into Java representation.
     * @param fo result set fetch direction, in OCaml representation
     * @return the passed result set fetch direction, in Java representation
     */
    static int decodeResultSetFetchDirection(final Value fo) {
        switch (fo.asLong()) {
        case 0: return ResultSet.FETCH_FORWARD;
        case 1: return ResultSet.FETCH_REVERSE;
        case 2: return ResultSet.FETCH_UNKNOWN;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeResultSetFetchDirection(Value)'

    /**
     * Converts a result set holdability from Java into OCaml representation.
     * @param h result set holdability, in Java representation
     * @return the passed result set holdability, in OCaml representation
     */
    static Value encodeResultSetHoldability(final int h) {
        switch (h) {
        case ResultSet.CLOSE_CURSORS_AT_COMMIT: return Value.ZERO;
        case ResultSet.HOLD_CURSORS_OVER_COMMIT: return Value.ONE;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeResultSetHoldability(int)'

    /**
     * Converts a result set holdability from OCaml into Java representation.
     * @param h result set holdability, in OCaml representation
     * @return the passed result set holdability, in Java representation
     */
    static int decodeResultSetHoldability(final Value h) {
        switch (h.asLong()) {
        case 0: return ResultSet.CLOSE_CURSORS_AT_COMMIT;
        case 1: return ResultSet.HOLD_CURSORS_OVER_COMMIT;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeResultSetHoldability(Value)'

    /**
     * Converts a result set type from Java into OCaml representation.
     * @param t result set type, in Java representation
     * @return the passed result set type, in OCaml representation
     */
    static Value encodeResultSetType(final int t) {
        switch (t) {
        case ResultSet.TYPE_FORWARD_ONLY: return Value.ZERO;
        case ResultSet.TYPE_SCROLL_INSENSITIVE: return Value.ONE;
        case ResultSet.TYPE_SCROLL_SENSITIVE: return Value.TWO;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeResultSetType(int)'

    /**
     * Converts a result set type from OCaml into Java representation.
     * @param t result set type, in OCaml representation
     * @return the passed result set type, in Java representation
     */
    static int decodeResultSetType(final Value t) {
        switch (t.asLong()) {
        case 0: return ResultSet.TYPE_FORWARD_ONLY;
        case 1: return ResultSet.TYPE_SCROLL_INSENSITIVE;
        case 2: return ResultSet.TYPE_SCROLL_SENSITIVE;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeResultSetType(Value)'

    /**
     * Converts a result scope from Java into OCaml representation.
     * @param s result scope, in Java representation
     * @return the passed result scope, in OCaml representation
     */
    static Value encodeResultScope(final int s) {
        switch (s) {
        case DatabaseMetaData.bestRowTemporary: return Value.ZERO;
        case DatabaseMetaData.bestRowTransaction: return Value.ONE;
        case DatabaseMetaData.bestRowSession: return Value.TWO;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeResultScope(int)'

    /**
     * Converts a result scope from OCaml into Java representation.
     * @param s result scope, in OCaml representation
     * @return the passed result scope, in Java representation
     */
    static int decodeResultScope(final Value s) {
        switch (s.asLong()) {
        case 0: return DatabaseMetaData.bestRowTemporary;
        case 1: return DatabaseMetaData.bestRowTransaction;
        case 2: return DatabaseMetaData.bestRowSession;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeResultScope(Value)'

    /**
     * Converts a statement action from Java into OCaml representation.
     * @param a statement action, in Java representation
     * @return the passed statement action, in OCaml representation
     */
    static Value encodeStatementAction(final int a) {
        switch (a) {
        case Statement.CLOSE_ALL_RESULTS: return Value.ZERO;
        case Statement.CLOSE_CURRENT_RESULT: return Value.ONE;
        case Statement.KEEP_CURRENT_RESULT: return Value.TWO;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeStatementAction(int)'

    /**
     * Converts a statement action from OCaml into Java representation.
     * @param a statement action, in OCaml representation
     * @return the passed statement action, in Java representation
     */
    static int decodeStatementAction(final Value a) {
        switch (a.asLong()) {
        case 0: return Statement.CLOSE_ALL_RESULTS;
        case 1: return Statement.CLOSE_CURRENT_RESULT;
        case 2: return Statement.KEEP_CURRENT_RESULT;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeStatementAction(Value)'

    /**
     * Converts a statement autogen from Java into OCaml representation.
     * @param ak statement autogen, in Java representation
     * @return the passed statement autogen, in OCaml representation
     */
    static Value encodeStatementAutogenKeys(final int ak) {
        switch (ak) {
        case Statement.NO_GENERATED_KEYS: return Value.ZERO;
        case Statement.RETURN_GENERATED_KEYS: return Value.ONE;
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeStatementAutogenKeys(int)'

    /**
     * Converts a statement autogen from OCaml into Java representation.
     * @param ak statement autogen, in OCaml representation
     * @return the passed statement autogen, in Java representation
     */
    static int decodeStatementAutogenKeys(final Value ak) {
        switch (ak.asLong()) {
        case 0: return Statement.NO_GENERATED_KEYS;
        case 1: return Statement.RETURN_GENERATED_KEYS;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeStatementAutogenKeys(Value)'

    /**
     * Converts a statement result from Java into OCaml representation.
     * @param r statement result, in Java representation
     * @return the passed statement result, in OCaml representation
     */
    static Value encodeStatementResult(final int r) {
        switch (r) {
        case Statement.EXECUTE_FAILED: return Value.ZERO;
        case Statement.SUCCESS_NO_INFO: return Value.ONE;
        default:
            final Block v = Block.createCustom(Custom.INT_32_SIZE,
                                               Custom.INT_32_OPS);
            v.setInt32(r);
            final Block res = Block.createBlock(0, Value.createFromBlock(v));
            return Value.createFromBlock(res);
        } // end switch
    } // end method 'encodeStatementResult(int)'

    /**
     * Converts a statement result from OCaml into Java representation.
     * @param r statement result, in OCaml representation
     * @return the passed statement result, in Java representation
     */
    static int decodeStatementResult(final Value r) {
        if (r == Value.ZERO) {
            return Statement.EXECUTE_FAILED;
        } else if (r == Value.ONE) {
            return Statement.SUCCESS_NO_INFO;
        } else {
            return r.asBlock().get(0).asBlock().asInt32();
        } // end if/elsif/else
    } // end method 'decodeStatementResult(Value)'

    /**
     * Converts a parameter mode from Java into OCaml representation.
     * @param pm parameter mode, in Java representation
     * @return the passed parameter mode, in OCaml representation
     */
    static Value encodeParameterMode(final int pm) {
        switch (pm) {
        case ParameterMetaData.parameterModeIn: return Value.ZERO;
        case ParameterMetaData.parameterModeOut: return Value.ONE;
        case ParameterMetaData.parameterModeInOut: return Value.TWO;
        case ParameterMetaData.parameterModeUnknown: return Value.createFromLong(3);
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeParameterMode(int)'

    /**
     * Converts a parameter mode from OCaml into Java representation.
     * @param pm parameter mode, in OCaml representation
     * @return the passed parameter mode, in Java representation
     */
    static int decodeParameterMode(final Value pm) {
        switch (pm.asLong()) {
        case 0: return ParameterMetaData.parameterModeIn;
        case 1: return ParameterMetaData.parameterModeOut;
        case 2: return ParameterMetaData.parameterModeInOut;
        case 3: return ParameterMetaData.parameterModeUnknown;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeParameterMode(Value)'

    /**
     * Converts a nullable from Java into OCaml representation.
     * @param nk nullable kind - should not be <tt>null</tt>
     * @param n nullable, in Java representation
     * @return the passed nullable, in OCaml representation
     */
    static Value encodeNullable(final NullableKind nk, final int n) {
        assert nk != null : "null nk";
        switch (nk) {
        case RESULT_SET_META_DATA:
            switch (n) {
            case ResultSetMetaData.columnNoNulls: return Value.ZERO;
            case ResultSetMetaData.columnNullable: return Value.ONE;
            case ResultSetMetaData.columnNullableUnknown: return Value.TWO;
            default:
                // never reached
                assert false : "invalid case";
                return Value.ZERO;
            } // end switch
        case PARAMETER_META_DATA:
            switch (n) {
            case ParameterMetaData.parameterNoNulls: return Value.ZERO;
            case ParameterMetaData.parameterNullable: return Value.ONE;
            case ParameterMetaData.parameterNullableUnknown: return Value.TWO;
            default:
                // never reached
                assert false : "invalid case";
                return Value.ZERO;
            } // end switch
        default:
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end switch
    } // end method 'encodeNullable(NullableKind, int)'

    /**
     * Converts a nullable from OCaml into Java representation.
     * @param nk nullable kind - should not be <tt>null</tt>
     * @param n nullable, in OCaml representation
     * @return the passed nullable, in Java representation
     */
    static int decodeNullable(final NullableKind nk, final Value n) {
        assert nk != null : "null nk";
        switch (nk) {
        case RESULT_SET_META_DATA:
            switch (n.asLong()) {
            case 0: return ResultSetMetaData.columnNoNulls;
            case 1: return ResultSetMetaData.columnNullable;
            case 2: return ResultSetMetaData.columnNullableUnknown;
            default:
                // never reached
                assert false : "invalid case";
                return 0;
            } // end switch
        case PARAMETER_META_DATA:
            switch (n.asLong()) {
            case 0: return ParameterMetaData.parameterNoNulls;
            case 1: return ParameterMetaData.parameterNullable;
            case 2: return ParameterMetaData.parameterNullableUnknown;
            default:
                // never reached
                assert false : "invalid case";
                return 0;
            } // end switch
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeNullable(NullableKind, Value)'

    /**
     * Converts a SQL state type from Java into OCaml representation.
     * @param sst SQL state type, in Java representation
     * @return the passed SQL state type, in OCaml representation
     */
    static Value encodeSQLStateType(final int sst) {
        if (sst == DatabaseMetaData.sqlStateXOpen) {
            return Value.ZERO;
        } else if (sst == DatabaseMetaData.sqlStateSQL) {
            return Value.ONE;
        } else if (sst == DatabaseMetaData.sqlStateSQL99) {
            return Value.TWO;
        } else {
            // never reached
            assert false : "invalid case";
            return Value.ZERO;
        } // end if/elsif/else
    } // end method 'encodeSQLStateType(int)'

    /**
     * Converts a SQL state type from OCaml into Java representation.
     * @param sst SQL state type, in OCaml representation
     * @return the passed SQL state type, in Java representation
     */
    static int decodeSQLStateType(final Value sst) {
        switch (sst.asLong()) {
        case 0: return DatabaseMetaData.sqlStateXOpen;
        case 1: return DatabaseMetaData.sqlStateSQL;
        case 2: return DatabaseMetaData.sqlStateSQL99;
        default:
            // never reached
            assert false : "invalid case";
            return 0;
        } // end switch
    } // end method 'decodeSQLStateType(Value)'

    /**
     * Converts a chained SQL warning from Java into OCaml representation.
     * @param warnings chained SQL warning, in Java representation
     * @return the passed chained SQL warning, as an OCaml list
     */
    static Value encodeSQLWarnings(final SQLWarning warnings) {
        final List<SQLWarning> l = new LinkedList<SQLWarning>();
        SQLWarning curr = warnings;
        while (curr != null) {
            l.add(curr);
            curr = curr.getNextWarning();
        } // end while
        Value res = Value.EMPTY_LIST;
        for (SQLWarning w : l) {
            final Value s1 = Value.createFromBlock(Block.createString(w.getMessage()));
            final Value s2 = Value.createFromBlock(Block.createString(w.getSQLState()));
            final Block b = Block.createBlock(0, s1, s2);
            final Value v = Value.createFromBlock(b);
            final Block cons = Block.createBlock(Block.TAG_CONS, v, res);
            res = Value.createFromBlock(cons);
        } // end for
        return res;
    } // end method 'encodeSQLWarnings(SQLWarning)'

    /**
     * Converts a driver property info from Java into OCaml representation.
     * @param dpi driver property info, in Java representation
     * @return the passed driver property info, in OCaml representation
     */
    static Value encodeDriverPropertyInfo(final DriverPropertyInfo dpi) {
        Value l = Value.EMPTY_LIST;
        for (String s : dpi.choices) {
            final Value v = Value.createFromBlock(Block.createString(s));
            final Block cons = Block.createBlock(Block.TAG_CONS, v, l);
            l = Value.createFromBlock(cons);
        } // end for
        final Block res = Block.createBlock(5, 0);
        res.set(0, l);
        res.set(1, Value.createFromBlock(Block.createString(dpi.description)));
        res.set(2, Value.createFromBlock(Block.createString(dpi.name)));
        res.set(3, dpi.required ? Value.TRUE : Value.FALSE);
        res.set(4, Value.createFromBlock(Block.createString(dpi.value)));
        return Value.createFromBlock(res);
    } // end method 'encodeDriverPropertyInfo(DriverPropertyInfo)'

    /**
     * Converts a driver property info from OCaml into Java representation.
     * @param dpi driver property info, in OCaml representation
     * @return the passed driver property info, in Java representation
     */
    static DriverPropertyInfo decodeDriverPropertyInfo(final Value dpi) {
        final List<String> c = new ArrayList<String>();
        final Block block = dpi.asBlock();
        Value list = block.get(0);
        while (list != Value.EMPTY_LIST) {
            final Block b = list.asBlock();
            c.add(b.get(0).asBlock().asString());
            list = b.get(1);
        } // end while
        final DriverPropertyInfo res =
            new DriverPropertyInfo(block.get(2).asBlock().asString(),
                                   block.get(4).asBlock().asString());
        res.choices = c.toArray(new String[c.size()]);
        res.description = block.get(1).asBlock().asString();
        res.required = block.get(3) == Value.TRUE;
        return res;
    } // end method 'decodeDriverPropertyInfo(Value)'

    /**
     * Converts properties from Java into OCaml representation.
     * @param p properties, in Java representation
     * @return the passed properties, in OCaml representation
     */
    static Value encodeProperties(final Properties p) {
        Value res = Value.EMPTY_LIST;
        for (Map.Entry<Object, Object> me : p.entrySet()) {
            final Block key = Block.createString(me.getKey().toString());
            final Block value = Block.createString(me.getValue().toString());
            final Block cpl = Block.createBlock(0,
                                                Value.createFromBlock(key),
                                                Value.createFromBlock(value));
            final Block cons = Block.createBlock(Block.TAG_CONS,
                                                 Value.createFromBlock(cpl),
                                                 res);
            res = Value.createFromBlock(cons);
        } // end for
        return res;
    } // end method 'encodeProperties(Properties)'

    /**
     * Converts properties from OCaml into Java representation.
     * @param v properties, in OCaml representation
     * @return the passed properties, in Java representation
     */
    static Properties decodeProperties(final Value v) {
        final Properties res = new Properties();
        Value list = v;
        while (list != Value.EMPTY_LIST) {
            final Block b = list.asBlock();
            final Block cpl = b.get(0).asBlock();
            res.setProperty(cpl.get(0).asBlock().asString(),
                            cpl.get(1).asBlock().asString());
            list = b.get(1);
        } // end while
        return res;
    } // end method 'decodeProperties(Value)'

    /**
     * Converts a Java array of statement results into an equivalent OCaml list.
     * @param v Java array of statement results
     * @return the corresponding OCaml list
     */
    static Value encodeStatementResultList(final int[] v) {
        Value res = Value.EMPTY_LIST;
        for (int i = v.length - 1; i >= 0; i--) {
            final Block cons = Block.createBlock(Block.TAG_CONS,
                                                 encodeStatementResult(v[i]),
                                                 res);
            res = Value.createFromBlock(cons);
        } // end for
        return res;
    } // end method 'encodeStatementResultList(int[])'

    /**
     * Converts an OCaml integer list into a Java integer array.
     * @param v OCaml integer list
     * @return the corresponding Java integer array
     */
    static int[] decodeIntList(final Value v) {
        final List<Integer> res = new ArrayList<Integer>();
        Value list = v;
        while (list.isBlock()) {
            final Block listBlock = list.asBlock();
            res.add(listBlock.get(0).asLong());
            list = listBlock.get(1);
        } // end while
        final int len = res.size();
        final int[] tmp = new int[len];
        for (int i = 0; i < len; i++) {
            tmp[i] = res.get(i);
        } // end for
        return tmp;
    } // end method 'decodeIntList(Value)'

    /**
     * Converts an OCaml string list into a Java string array.
     * @param v OCaml string list
     * @return the corresponding Java string array
     */
    static String[] decodeStringList(final Value v) {
        final List<String> res = new ArrayList<String>();
        Value list = v;
        while (list.isBlock()) {
            final Block listBlock = list.asBlock();
            res.add(listBlock.get(0).asBlock().asString());
            list = listBlock.get(1);
        } // end while
        return res.toArray(new String[res.size()]);
    } // end method 'decodeStringList(Value)'

} // end class 'Misc'
